﻿package com.pandemicode.framework.controls {
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.ui.Mouse;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getDefinitionByName;
	import org.osflash.signals.natives.NativeSignal;
	
	/**
	* @author Michael Lawrence
	* @version 2.0.0
	*/
	public class Cursor {
		private var _cursor:Sprite;
		
		private var _mouseMove:NativeSignal;
		
		private var _cursorOffset:Point = new Point(0, 0);
		/**
		 * The X & Y offset for displaying the cursor from the mouse position.
		 */
		public function get cursorOffset():Point { return this._cursorOffset; }
		public function set cursorOffset(value:Point):void {
			this._cursorOffset = value;
		}
		
		private var _stage:Stage;
		/**
		 * The reference to the stage object that we'll use to add the cursor to.  We'll also be using this stage reference's coordinate space.
		 */
		public function get stage():Stage { return this._stage; }
		public function set stage(value:Stage):void {
			if (this._stage != null) {
				if (this._stage.contains(this._cursor)) {
					this._stage.removeChild(this._cursor);
				}
			}
			
			this._stage = value;
			this._stage.addChild(this._cursor);
		}
		
		private var _shown:Boolean = false;
		/**
		 * When set to true it shows the cursor and hides the mouse. When set to false it hides the cursor and shows the mouse.
		 * @default false
		 */
		public function get shown():Boolean { return this._shown; }
		public function set shown(value:Boolean):void {
			if (this._stage == null)
				throw new Error("You must first set the StageReference property before showing the cursor.");
			
			this._shown = value;
			this._cursor.visible = value;
			
			if (value) {
				Mouse.hide();
				this._mouseMove.add(this._onMouseMove);
			} else {
				Mouse.show();
				this._mouseMove.remove(this._onMouseMove);
			}
		}
		
		/**
		 * This sets the DisplayObject to be displayed when Shown is set to true.
		 */
		public function set graphic(value:DisplayObject):void {
			this.graphicClassName = getQualifiedClassName(value);
		}
		
		/**
		 * This sets the DisplayObject to be displayed when Shown is set to true.
		 */
		public function set graphicClassName(value:String):void {
			var c:Class = getDefinitionByName(value) as Class;
			this.graphicClass = c;
		}
		
		/**
		 * This sets the DisplayObject to be displayed when Shown is set to true.
		 */
		public function set graphicClass(value:Class):void {
			var o:* = new value();
			if(o is DisplayObject) {
				var old:DisplayObject = this._cursor.getChildByName("selected");
				if (old != null) this._cursor.removeChild(old);
				var d:DisplayObject = o as DisplayObject;
				d.name = "selected";
				this._cursor.addChild(d);
			} else {
				throw new Error("Only classes that inherit the flash.display.DisplayObject class are supported.");
			}
		}
		
		private static const _instance:Cursor = new Cursor(SingletonLock);
		/**
		 * A reference to the Singleton instance.
		 */
		public static function get instance():Cursor {
			return _instance;
		}
		
		/**
		 * A cursor object.  This class cannot be instantiated. Instead use Cursor.instance.
		 * @param	lock	The singleton lock.
		 */
		public function Cursor(lock:Class):void {
			// Verify that the lock is the correct class reference.
			if ( lock != SingletonLock )
				throw new Error("Invalid Singleton access.  Use Cursor.Instance." );
				
			this._mouseMove = new NativeSignal(this.stage, MouseEvent.MOUSE_MOVE, MouseEvent);
				
			this._cursor = new Sprite();
			this._cursor.visible =
			this._cursor.mouseEnabled =
			this._cursor.mouseChildren =
			this._shown = false;
		}
		
		private function _onMouseMove(e:MouseEvent):void {
			if(this._shown) {
				this._cursor.x = e.stageX + this._cursorOffset.x;
				this._cursor.y = e.stageY + this._cursorOffset.y;
			}
		}
	}
}

/*
* This is a private class declared outside of the package
* that is only accessible to classes inside of the GameTimer.as
* file.  Because of that, no outside code is able to get a
* reference to this class to pass to the constructor, which
* enables us to prevent outside instantiation.
*/
class SingletonLock { }